package net.gdface.thrifty;

import static com.facebook.swift.codec.metadata.FieldKind.THRIFT_FIELD;
import static com.google.common.base.Preconditions.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;

import com.facebook.swift.codec.ThriftField.Requiredness;
import com.facebook.swift.codec.metadata.ThriftCatalog;
import com.facebook.swift.codec.metadata.ThriftCatalogWithTransformer;
import com.facebook.swift.codec.metadata.ThriftConstructorInjection;
import com.facebook.swift.codec.metadata.ThriftFieldMetadata;
import com.facebook.swift.codec.metadata.ThriftParameterInjection;
import com.facebook.swift.codec.metadata.ThriftStructMetadata;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.microsoft.thrifty.Struct;
import com.microsoft.thrifty.ThriftException;

import net.gdface.thrift.BaseThriftUtils;
import net.gdface.thrift.ThriftDecorator;
import net.gdface.thrift.TypeValue;

/**
 * thrift工具
 * @author guyadong
 *
 */
public class ThriftUtils extends BaseThriftUtils{
	public static final ThriftCatalog CATALOG = new ThriftCatalogWithTransformer();
	public static final String DECORATOR_PKG_SUFFIX="decorator";
	public static final String CLIENT_SUFFIX="client";
	public static final String DECORATOR_CLIENT_PKG_SUFFIX= DECORATOR_PKG_SUFFIX + "." + CLIENT_SUFFIX;
	public ThriftUtils() {
	}

	/**
	 * 构造{@code metadata}指定类型的实例并填充字段
	 * @param data
	 * @param metadata
	 * @return instance always
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	public static <T>T constructStruct(Map<String, TypeValue> data,ThriftStructMetadata metadata) 
		throws Exception{
		T instance;
	    {
	        ThriftConstructorInjection constructor = metadata.getConstructorInjection().get();
	        Type[] dstTypes = constructor.getConstructor().getGenericParameterTypes();
	        Type[] srcTypes = new Type[constructor.getParameters().size()];
	        Object[] parametersValues = new Object[constructor.getParameters().size()];
	        checkState(dstTypes.length == parametersValues.length);
	        for (ThriftParameterInjection parameter : constructor.getParameters()) {
	        	TypeValue value = data.get(parameter.getId());
	            parametersValues[parameter.getParameterIndex()] = value.value;
	            srcTypes[parameter.getParameterIndex()] = value.type;
	        }
	        for(int i =0;i<dstTypes.length;++i){
	        	parametersValues[i] = TypeTransformer.getInstance().cast(parametersValues[i], srcTypes[i], dstTypes[i]);
	        }
	        try {
	            instance = (T) constructor.getConstructor().newInstance(parametersValues);
	        } catch (InvocationTargetException e) {
	            if (e.getTargetException() != null) {
	            	Throwables.throwIfUnchecked(e.getTargetException());
	            	throw new RuntimeException(e.getTargetException());
	            }
	            throw e;
	        } 
	    }
		return fillStructField(data,metadata,instance);
	}

	/**
	 * 填充{@code instance}实例的字段<br>
	 * @param data
	 * @param metadata
	 * @param instance
	 * @return instance always
	 * @throws Exception
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static <T>T fillStructField(Map<String, TypeValue> data,ThriftStructMetadata metadata,T instance) 
		throws Exception{
		return (T) fillStructField((Map)data,metadata,instance,TypeTransformer.getInstance(),false);
	}

	/**
	 * 根据{@code metadata}类型数据获取{@code instance}实例所有的字段值
	 * @param instance
	 * @param metadata
	 * @return 字段值映射表
	 */
	public static Map<String, TypeValue> getFieldValues(Object instance, ThriftStructMetadata metadata) {
		checkArgument(null != instance && null != metadata && metadata.getStructClass().isInstance(instance), 
				"instance,metadata must not be null");
		
		Collection<ThriftFieldMetadata> fields = metadata.getFields(THRIFT_FIELD);
		Map<String, TypeValue> data = new HashMap<>(fields.size());
		for (ThriftFieldMetadata field : fields) {
			try {
	            // is the field readable?
	            if (field.isWriteOnly()) {
	                continue;
	            }
				TypeValue value = getFieldValue(instance, field);
				if (value.value == null) {
					if (field.getRequiredness() == Requiredness.REQUIRED) {
						throw new RuntimeException("required field was not set");
					} else {
						continue;
					}
				}
				data.put(field.getName(), value);
			} catch (Exception e) {
				Throwables.throwIfUnchecked(e);
				throw new RuntimeException(e);
			}
		}
		return data;
	}


	public static boolean isThriftyStruct(Type type){
		return type instanceof Class<?> 
			? Struct.class.isAssignableFrom((Class<?>)type)
			: false;
	}






	public static boolean isThriftyException(Type type){
		return isException(type) && isThriftyStruct(type);
	}



	/**
	 * 返回 {@code left & right}之间的decorator类型,如果{@code right}不为thrift struct则返回{@code null}
	 * @param left 原始类型
	 * @param right {@code left}对应的client端存根类型
	 * @return decorator类型
	 */
	public static <L,M extends ThriftDecorator<L>,R>Class<M> getMiddleClass(Class<L>left,Class<R>right){
		if(isThriftyStruct(right)){
			Class<M> decoratorClass = getDecoratorType(left);
			if(null != decoratorClass && decoratorClass.getSimpleName().equals(left.getSimpleName())){
				return decoratorClass;
			}			
		}
		return null;
	}

	/** 避免{@code null}抛出异常 */
	public static <T> T returnNull(ThriftException e){
		if(e.kind == ThriftException.Kind.MISSING_RESULT  ){
            return null;
        }
	    throw e;
	}
	/** 避免{@code null}抛出异常 
	 * @throws Throwable */
	public static <T> T returnNull(Throwable e) throws Throwable{
		if(e instanceof ThriftException){
			return returnNull((ThriftException)e);
		}
	    throw e;
	}
	public static<V> void addCallback(
			final ListenableFuture<V> future,
			final FutureCallback<? super V> callback,Executor executor) {
		checkArgument(null != callback,"callback is null");
		checkArgument(null != executor,"executor is null");
		Runnable callbackListener =
				new Runnable() {
			@Override
			public void run() {
				V value;
				try {
					value = Futures.getDone(future);
				} catch (ExecutionException e) {
					try{
						// value is null
						value = returnNull(e.getCause()); 
					}catch(Throwable t){
						callback.onFailure(t);
						return;
					}                    
				} catch (Throwable e) {
					callback.onFailure(e);
					return;
				}
				callback.onSuccess(value);
			}
		};
		future.addListener(callbackListener, executor);
	}

}
